from __future__ import annotations

from django.db.models.signals import post_save

from sentry import analytics
from sentry.adoption import manager
from sentry.models.featureadoption import FeatureAdoption
from sentry.models.group import Group
from sentry.models.grouptombstone import GroupTombstone
from sentry.models.organization import Organization
from sentry.models.project import Project
from sentry.plugins.bases.issue import IssueTrackingPlugin
from sentry.plugins.bases.issue2 import IssueTrackingPlugin2
from sentry.plugins.bases.notify import NotificationPlugin
from sentry.receivers.rules import (
    DEFAULT_RULE_DATA,
    DEFAULT_RULE_DATA_NEW,
    DEFAULT_RULE_LABEL,
    DEFAULT_RULE_LABEL_NEW,
)
from sentry.services.hybrid_cloud.integration import integration_service
from sentry.signals import (
    advanced_search,
    advanced_search_feature_gated,
    alert_rule_created,
    alert_rule_edited,
    data_scrubber_enabled,
    deploy_created,
    event_processed,
    first_event_received,
    inbound_filter_toggled,
    integration_added,
    integration_issue_created,
    integration_issue_linked,
    issue_archived,
    issue_assigned,
    issue_deleted,
    issue_escalating,
    issue_ignored,
    issue_mark_reviewed,
    issue_resolved,
    issue_unignored,
    issue_unresolved,
    issue_update_priority,
    member_joined,
    monitor_environment_failed,
    ownership_rule_created,
    plugin_enabled,
    project_created,
    release_created,
    repo_linked,
    save_search_created,
    sso_enabled,
    team_created,
    transaction_processed,
    user_feedback_received,
)
from sentry.utils import metrics
from sentry.utils.javascript import has_sourcemap

DEFAULT_TAGS = frozenset(
    [
        "level",
        "logger",
        "transaction",
        "url",
        "browser",
        "sentry:user",
        "os",
        "server_name",
        "device",
        "os.name",
        "browser.name",
        "sentry:release",
        "environment",
        "device.family",
        "site",
        "version",
        "interface_type",
        "rake_task",
        "runtime",
        "runtime.name",
        "type",
        "php_version",
        "app",
        "app.device",
        "locale",
        "os_version",
        "device_model",
        "deviceModel",
        "sentry_version",
    ]
)


# First Event
@first_event_received.connect(weak=False)
def record_first_event(project, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=project.organization_id, feature_slug="first_event", complete=True
    )


def record_event_processed(project, event, **kwargs):
    feature_slugs = []

    platform = event.group.platform if event.group else event.platform

    # Platform
    if platform in manager.location_slugs("language"):
        feature_slugs.append(platform)

    # Release Tracking
    if event.get_tag("sentry:release"):
        feature_slugs.append("release_tracking")

    # Environment Tracking
    if event.get_tag("environment"):
        feature_slugs.append("environment_tracking")

    # User Tracking
    user_context = event.data.get("user")
    # We'd like them to tag with id or email.
    # Certain SDKs automatically tag with ip address.
    # Check to make sure more the ip address is being sent.
    # testing for this in test_no_user_tracking_for_ip_address_only
    # list(d.keys()) pattern is to make this python3 safe
    if user_context and len(user_context.keys() - {"ip_address", "sentry_user"}) > 0:
        feature_slugs.append("user_tracking")

    # Custom Tags
    if {tag[0] for tag in event.tags} - DEFAULT_TAGS:
        feature_slugs.append("custom_tags")

    # Sourcemaps
    if has_sourcemap(event):
        feature_slugs.append("source_maps")

    # Breadcrumbs
    if event.data.get("breadcrumbs"):
        feature_slugs.append("breadcrumbs")

    if not feature_slugs:
        return

    FeatureAdoption.objects.bulk_record(project.organization_id, feature_slugs)


event_processed.connect(record_event_processed, weak=False)
transaction_processed.connect(record_event_processed, weak=False)


@user_feedback_received.connect(weak=False)
def record_user_feedback(project, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=project.organization_id, feature_slug="user_feedback", complete=True
    )


@project_created.connect(weak=False)
def record_project_created(project, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=project.organization_id, feature_slug="first_project", complete=True
    )


@member_joined.connect(weak=False)
def record_member_joined(organization_id: int, user_id: int, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=organization_id, feature_slug="invite_team", complete=True
    )
    analytics.record("organization.joined", user_id=user_id, organization_id=organization_id)


@issue_assigned.connect(weak=False)
def record_issue_assigned(project, group, user, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=project.organization_id, feature_slug="assignment", complete=True
    )

    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = project.organization.get_default_owner().id
    analytics.record(
        "issue.assigned",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=project.organization_id,
        group_id=group.id,
    )


@issue_resolved.connect(weak=False)
def record_issue_resolved(organization_id, project, group, user, resolution_type, **kwargs):
    """There are three main types of ways to resolve issues
    1) via a release (current release, next release, or other)
    2) via commit (in the UI with the commit hash (marked as "in_commit")
        or tagging the issue in a commit (marked as "with_commit"))
    3) now
    """
    if resolution_type in ("in_next_release", "in_release"):
        FeatureAdoption.objects.record(
            organization_id=organization_id, feature_slug="resolved_in_release", complete=True
        )
    if resolution_type == "with_commit":
        FeatureAdoption.objects.record(
            organization_id=organization_id, feature_slug="resolved_with_commit", complete=True
        )

    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = project.organization.get_default_owner().id

    analytics.record(
        "issue.resolved",
        user_id=user_id,
        project_id=project.id,
        default_user_id=default_user_id,
        organization_id=organization_id,
        group_id=group.id,
        resolution_type=resolution_type,
        issue_type=group.issue_type.slug,
        issue_category=group.issue_category.name.lower(),
    )


@issue_unresolved.connect(weak=False)
def record_issue_unresolved(project, user, group, transition_type, **kwargs):
    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = project.organization.get_default_owner().id

    analytics.record(
        "issue.unresolved",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=project.organization_id,
        group_id=group.id,
        transition_type=transition_type,
    )


@advanced_search.connect(weak=False)
def record_advanced_search(project, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=project.organization_id, feature_slug="advanced_search", complete=True
    )


@advanced_search_feature_gated.connect(weak=False)
def record_advanced_search_feature_gated(user, organization, **kwargs):
    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = organization.get_default_owner().id

    analytics.record(
        "advanced_search.feature_gated",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=organization.id,
    )


# XXX(epurkhiser): This was originally used in project saved searches, but
# those no longer exist and this is no longer connected to anything. We
# probably want to connect this up to organization level saved searches.
@save_search_created.connect(weak=False)
def record_save_search_created(project, user, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=project.organization_id, feature_slug="saved_search", complete=True
    )

    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = project.organization.get_default_owner().id

    analytics.record(
        "search.saved",
        user_id=user_id,
        default_user_id=default_user_id,
        project_id=project.id,
        organization_id=project.organization_id,
    )


@inbound_filter_toggled.connect(weak=False)
def record_inbound_filter_toggled(project, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=project.organization_id, feature_slug="inbound_filters", complete=True
    )


@alert_rule_created.connect(weak=False)
def record_alert_rule_created(
    user,
    project,
    rule,
    rule_type,
    is_api_token,
    referrer=None,
    session_id=None,
    alert_rule_ui_component=None,
    duplicate_rule=None,
    wizard_v3=None,
    **kwargs,
):
    if (
        rule_type == "issue"
        and rule.label in [DEFAULT_RULE_LABEL, DEFAULT_RULE_LABEL_NEW]
        and rule.data in [DEFAULT_RULE_DATA, DEFAULT_RULE_DATA_NEW]
    ):
        return

    FeatureAdoption.objects.record(
        organization_id=project.organization_id, feature_slug="alert_rules", complete=True
    )

    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = project.organization.get_default_owner().id

    analytics.record(
        "alert.created",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=project.organization_id,
        project_id=project.id,
        rule_id=rule.id,
        rule_type=rule_type,
        referrer=referrer,
        session_id=session_id,
        is_api_token=is_api_token,
        alert_rule_ui_component=alert_rule_ui_component,
        duplicate_rule=duplicate_rule,
        wizard_v3=wizard_v3,
    )


@alert_rule_edited.connect(weak=False)
def record_alert_rule_edited(
    user,
    project,
    rule,
    rule_type,
    is_api_token,
    **kwargs,
):
    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = project.organization.get_default_owner().id

    analytics.record(
        "alert.edited",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=project.organization_id,
        project_id=project.id,
        rule_id=rule.id,
        rule_type=rule_type,
        is_api_token=is_api_token,
    )


@plugin_enabled.connect(weak=False)
def record_plugin_enabled(plugin, project, user, **kwargs):
    if isinstance(plugin, (IssueTrackingPlugin, IssueTrackingPlugin2)):
        FeatureAdoption.objects.record(
            organization_id=project.organization_id,
            feature_slug="issue_tracker_integration",
            complete=True,
        )
    elif isinstance(plugin, NotificationPlugin):
        FeatureAdoption.objects.record(
            organization_id=project.organization_id,
            feature_slug="notification_integration",
            complete=True,
        )


@sso_enabled.connect(weak=False)
def record_sso_enabled(organization_id, user_id, provider, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=organization_id, feature_slug="sso", complete=True
    )

    analytics.record(
        "sso.enabled", user_id=user_id, organization_id=organization_id, provider=provider
    )


@data_scrubber_enabled.connect(weak=False)
def record_data_scrubber_enabled(organization, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=organization.id, feature_slug="data_scrubbers", complete=True
    )


def deleted_and_discarded_issue(instance, created, **kwargs):
    if created:
        FeatureAdoption.objects.record(
            organization_id=instance.project.organization_id, feature_slug="delete_and_discard"
        )


@repo_linked.connect(weak=False)
def record_repo_linked(repo, user, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=repo.organization_id, feature_slug="repo_linked", complete=True
    )

    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = Organization.objects.get(id=repo.organization_id).get_default_owner().id

    analytics.record(
        "repo.linked",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=repo.organization_id,
        repository_id=repo.id,
        provider=repo.provider,
    )


@release_created.connect(weak=False)
def record_release_created(release, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=release.organization_id, feature_slug="release_created", complete=True
    )


@deploy_created.connect(weak=False)
def record_deploy_created(deploy, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=deploy.organization_id, feature_slug="deploy_created", complete=True
    )


@ownership_rule_created.connect(weak=False)
def record_ownership_rule_created(project, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=project.organization_id,
        feature_slug="ownership_rule_created",
        complete=True,
    )


@issue_ignored.connect(weak=False)
def record_issue_ignored(project, user, group_list, activity_data, **kwargs):
    FeatureAdoption.objects.record(
        organization_id=project.organization_id, feature_slug="issue_ignored", complete=True
    )

    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = project.organization.get_default_owner().id

    for group in group_list:
        analytics.record(
            "issue.ignored",
            user_id=user_id,
            default_user_id=default_user_id,
            organization_id=project.organization_id,
            group_id=group.id,
            ignore_duration=activity_data.get("ignoreDuration"),
            ignore_count=activity_data.get("ignoreCount"),
            ignore_window=activity_data.get("ignoreWindow"),
            ignore_user_count=activity_data.get("ignoreUserCount"),
            ignore_user_window=activity_data.get("ignoreUserWindow"),
        )


@issue_archived.connect(weak=False)
def record_issue_archived(project, user, group_list, activity_data, **kwargs):
    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = project.organization.get_default_owner().id

    for group in group_list:
        analytics.record(
            "issue.archived",
            user_id=user_id,
            default_user_id=default_user_id,
            organization_id=project.organization_id,
            group_id=group.id,
            until_escalating=activity_data.get("until_escalating"),
        )


@issue_escalating.connect(weak=False)
def record_issue_escalating(project, group, event, was_until_escalating, **kwargs):
    analytics.record(
        "issue.escalating",
        organization_id=project.organization_id,
        project_id=project.id,
        group_id=group.id,
        event_id=event.event_id if event else None,
        was_until_escalating=was_until_escalating,
    )


@issue_update_priority.connect(weak=False)
def record_update_priority(
    project: Project,
    group: Group,
    new_priority: str,
    previous_priority: str | None,
    user_id: int | None,
    reason: str | None,
    **kwargs,
):
    analytics.record(
        "issue.priority_updated",
        group_id=group.id,
        new_priority=new_priority,
        organization_id=project.organization_id,
        project_id=project.id if project else None,
        user_id=user_id,
        previous_priority=previous_priority,
        issue_category=group.issue_category.name.lower(),
        issue_type=group.issue_type.slug,
        reason=reason,
    )


@issue_unignored.connect(weak=False)
def record_issue_unignored(project, user_id, group, transition_type, **kwargs):
    if user_id is not None:
        default_user_id = user_id
    else:
        default_user_id = project.organization.get_default_owner().id

    analytics.record(
        "issue.unignored",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=project.organization_id,
        group_id=group.id,
        transition_type=transition_type,
    )


@issue_mark_reviewed.connect(weak=False)
def record_issue_reviewed(project, user, group, **kwargs):
    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = project.organization.get_default_owner().id

    analytics.record(
        "issue.mark_reviewed",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=project.organization_id,
        group_id=group.id,
    )


@team_created.connect(weak=False)
def record_team_created(
    organization=None,
    user=None,
    team=None,
    organization_id=None,
    user_id=None,
    team_id=None,
    **kwargs,
):
    if organization is None:
        organization = Organization.objects.get(id=organization_id)

    if team_id is None:
        team_id = team.id

    if user_id is None and user and user.is_authenticated:
        user_id = user.id
    if user_id is None:
        default_user_id = organization.get_default_owner().id
    else:
        default_user_id = user_id

    analytics.record(
        "team.created",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=organization.id,
        team_id=team_id,
    )


@integration_added.connect(weak=False)
def record_integration_added(
    integration_id: int, organization_id: int, user_id: int | None, **kwargs
):
    organization = Organization.objects.get(id=organization_id)
    integration = integration_service.get_integration(integration_id=integration_id)
    assert integration, f"integration_added called for missing integration: {integration_id}"

    if user_id is not None:
        default_user_id = user_id
    else:
        default_user_id = organization.get_default_owner().id

    analytics.record(
        "integration.added",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=organization.id,
        provider=integration.provider,
        id=integration.id,
    )
    metrics.incr(
        "integration.added",
        sample_rate=1.0,
        tags={"integration_slug": integration.provider},
    )


@integration_issue_created.connect(weak=False)
def record_integration_issue_created(integration, organization, user, **kwargs):
    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = organization.get_default_owner().id
    analytics.record(
        "integration.issue.created",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=organization.id,
        provider=integration.provider,
        id=integration.id,
    )


@integration_issue_linked.connect(weak=False)
def record_integration_issue_linked(integration, organization, user, **kwargs):
    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = organization.get_default_owner().id
    analytics.record(
        "integration.issue.linked",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=organization.id,
        provider=integration.provider,
        id=integration.id,
    )


@issue_deleted.connect(weak=False)
def record_issue_deleted(group, user, delete_type, **kwargs):
    if user and user.is_authenticated:
        user_id = default_user_id = user.id
    else:
        user_id = None
        default_user_id = group.project.organization.get_default_owner().id
    analytics.record(
        "issue.deleted",
        user_id=user_id,
        default_user_id=default_user_id,
        organization_id=group.project.organization_id,
        group_id=group.id,
        project_id=group.project_id,
        delete_type=delete_type,
    )


@monitor_environment_failed.connect(weak=False)
def record_monitor_failure(monitor_environment, **kwargs):
    analytics.record(
        "monitor_environment.mark_failed",
        organization_id=monitor_environment.monitor.organization_id,
        monitor_id=monitor_environment.monitor.guid,
        project_id=monitor_environment.monitor.project_id,
        environment_id=monitor_environment.environment_id,
    )


post_save.connect(
    deleted_and_discarded_issue,
    sender=GroupTombstone,
    dispatch_uid="analytics.grouptombstone.created",
    weak=False,
)
